#include "Cache.h"
#include <iostream>

#include "Page.h"

class FreeList  {
public:
    struct FreeNode {
        struct FreeNode * prev;
        struct FreeNode * next;
    };
    
    FreeList(const int pageSize);
    ~FreeList();

    int init(char * addr, const int length);
    void deinit();

    char * front();
    char * back();
    char * popFront();
    char * popBack();
    void pushFront(char * addr);
    void pushBack(char * addr);

    void show();

    const int32_t getPageCount() const { return pageCount;}
    const int64_t getBytes() const { return getPageCount() * pageSize;}
private:
    const int32_t pageSize;
    char * header;
    int32_t pageCount;
};


FreeList::FreeList(const int pageSize)
: pageSize(pageSize)
, header(nullptr)
, pageCount(0)
{
}
FreeList::~FreeList(){
}
int FreeList::init(char * addr, const int length){
    if(length >= pageSize && length%pageSize  == 0){
        pageCount = length/pageSize;

        char * curPagePtr = addr;
        char * lastPagePtr = addr + length - pageSize;
        for(int i = 0; i < pageCount; ++i){
            FreeNode * node = (FreeNode*)curPagePtr;
            node->next = (FreeNode*)(curPagePtr + pageSize);
            node->prev = (FreeNode*)lastPagePtr;
            lastPagePtr = curPagePtr;
            curPagePtr += pageSize; 
        }
        //handle final Page
        FreeNode * finalPage = (FreeNode*)(addr + length - pageSize);
        finalPage->next = (FreeNode*)addr;

        header = addr;
        return 0;
    }
    return -1;//arguments error
}
void FreeList::deinit(){
    header = nullptr;
    pageCount = 0;
}
void FreeList::show(){
    if(header != nullptr){
        char * curPagePtr = header;
        for(int i = 0; i < pageCount; ++i){
            FreeNode * node = (FreeNode*)curPagePtr;
            std::cout << "index:" << i
                << " cur:" << static_cast<const void*>(node)
                << " prev:" << static_cast<const void*>(node->prev)
                << " next:" << static_cast<const void*>(node->next)
                << std::endl;
            curPagePtr = (char*)(node->next);
        }
        std::cout << "pageAddr:" << static_cast<const void*>(header)
            << " pageCount:" << pageCount
            << std::endl;
        std::cout << "front: " << static_cast<const void*>(front())
                << " back: " << static_cast<const void*>(back())
                <<std::endl;
    }
    else{
        std::cout << "freeList is empty" << std::endl;
    }
}

char * FreeList::front(){
    return header;
}

char * FreeList::back(){
    if(header != nullptr){
        FreeNode * node = (FreeNode*)header;
        return (char*)node->prev;
    }
    return nullptr;
}


//Note: only one page, or no page
// change :  header , count, prev, next
char * FreeList::popFront(){
    if(pageCount >= 1){
        FreeNode * node = (FreeNode*)header;
        node->next->prev  = node->prev;
        node->prev->next = node->next;

        header = (pageCount>1) ? (char*)(node->next) : nullptr;
        --pageCount;
        return (char*)node;
    }
    //freeList is empty
    return nullptr;
}
// change: count &  prev, next
char * FreeList::popBack(){
    if(pageCount >= 1){
        FreeNode * node = ((FreeNode*)header)->prev;

        node->next->prev  = node->prev;
        node->prev->next = node->next;
        
        header = (pageCount==1) ? nullptr : header;
        --pageCount;
        return (char*)node;
    }
    return nullptr; //freeList is empty
}
//change: count & prev, next
void FreeList::pushFront(char * addr){
    if(addr != nullptr){
        if(header != nullptr){
            FreeNode * node = (FreeNode*)header;

            FreeNode * newNode = (FreeNode*)addr;
            newNode->next = node;
            newNode->prev = node->prev;

            node->prev = newNode;
            node->prev->next = newNode;
        }
        else { //header is nullptr
            FreeNode * node = (FreeNode*)addr;
            node->next = node;
            node->prev = node;
        }
        ++pageCount;
        header = addr;
    }
}
//change: count & prev, next
void FreeList::pushBack(char * addr){
    if(addr != nullptr){
        if(header != nullptr){
            FreeNode * node = (FreeNode*)header;

            FreeNode * newNode = (FreeNode*)addr;
            newNode->next = node;
            newNode->prev = node->prev;

            node->prev = newNode;
            node->prev->next = newNode;
        }
        else{
            FreeNode * node = (FreeNode*)addr;
            node->next = node;
            node->prev = node;
            header = addr;
        }
        ++pageCount;
    }
}


Cache::Cache(const int64_t cacheSize, const int32_t pageSize)
: cacheSize(cacheSize)
, pageSize(pageSize)
, pageAddr(nullptr)
, freeList(new FreeList(pageSize))
{
}
Cache::~Cache(){
    if(freeList != nullptr){
        delete freeList;
        freeList = nullptr;
    }
}

const int32_t Cache::freePageCount() const { 
    return freeList->getPageCount(); 
}
int Cache::init(){
    assert(cacheSize % pageSize == 0);

    pageAddr = (char*)::malloc(cacheSize);
    if(pageAddr == nullptr) return -1; //no enough memory

    if( 0 == freeList->init(pageAddr, cacheSize)){
        //freeList->show(); // for test
        return 0;
    }
    return -1;
}
void Cache::deinit(){
    auto it = pageMap.begin();
    for(; it != pageMap.end(); ++it){
        auto page = it->second;
        if(page != nullptr){
            freeList->pushBack(page->getAddr());
            delete page;
        }
        else{
            std::cerr << "fatal error, memory in pageMap is nullptr" << std::endl;
        }
    }
    pageMap.clear();

    freeList->deinit();

    ::free(pageAddr);
    pageAddr = nullptr;
}

Page * Cache::allocate(PageNumberType pageNo){
    if(!isAllocated(pageNo)){
        char * newPage = freeList->front();
        if(newPage != nullptr){
            freeList->popFront();

            auto page = new Page(pageNo, newPage, pageSize);
            pageMap[pageNo] = page;
            return page;
        }
        //no memory
        return nullptr;
    }
    //already allocated
    return nullptr;
}
Page * Cache::acquire(PageNumberType pageNo){
    auto it = pageMap.find(pageNo);
    return (it != pageMap.end()) ? it->second  : nullptr;
}

int Cache::release(Page * page){
    assert(page != nullptr); 

    auto it = pageMap.find(page->getNo());
    if(it != pageMap.end()){ //find
        pageMap.erase(it);

        freeList->pushBack(page->getAddr());
        delete page;
        return 0;
    }
    //pageNo not allocated
    return -1;
}

